﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
using SPMonkey.Model;
using SPMonkey.Caml;
using SPMonkey.Resources;


namespace SPMonkey.Core
{
    public static class SPExtensions
    {
        #region SPSite

        public static SPWeb OpenWebElevated(this SPSite site, string url)
        {
            SPWeb result = null;
            SPSecurity.RunWithElevatedPrivileges(() =>
            {
                using (SPSite elevatedSite = new SPSite(site.Url))
                    result = elevatedSite.OpenWeb(url);
            });
            return result;
        }

        public static SPWeb OpenWebElevated(this SPSite site, Guid id)
        {
            SPWeb result = null;
            SPSecurity.RunWithElevatedPrivileges(() =>
            {
                using (SPSite elevatedSite = new SPSite(site.Url))
                    result = elevatedSite.OpenWeb(id);
            });
            return result;
        }

        public static SPWeb OpenWebElevated(this SPSite site)
        {
            SPWeb result = null;
            SPSecurity.RunWithElevatedPrivileges(() =>
            {
                using (SPSite elevatedSite = new SPSite(site.Url))
                    result = elevatedSite.OpenWeb();
            });
            return result;
        }

        #endregion

        #region SPWeb extensions

        /// <summary>
        /// Get List by BaseTemplete type.
        /// </summary>
        public static SPList GetListByType(this SPWeb web, int type)
        {
            return web.Lists.Cast<SPList>().FirstOrDefault(list => list.BaseTemplate.ToString() == type.ToString());
        }

        /// <summary>
        /// Get List by unique id.
        /// </summary>
        public static SPList GetListByUniqueId(this SPWeb web, Guid id)
        {
            //try
            //{
            //    return web.Lists[id];
            //}
            //catch
            //{
            //    return null;
            //}
            return web.Lists.Cast<SPList>().FirstOrDefault(list => list.ID == id);
        }

        /// <summary>
        /// Get List by title.
        /// </summary>
        public static SPList GetListByTitle(this SPWeb web, string title)
        {
            //try
            //{
            //    return web.Lists[title];
            //}
            //catch
            //{
            //    return null;
            //}
            return web.Lists.Cast<SPList>().FirstOrDefault(list => list.Title == title);
        }

        /// <summary>
        /// Get List by internal name (web.Url + "/lists/" + internalname).
        /// </summary>
        public static SPList GetListByInternalName(this SPWeb web, string internalname)
        {
            return web.Lists.Cast<SPList>().FirstOrDefault(list => list.RootFolder.Name == internalname);
        }

        #endregion

        #region SPList extensions

        /// <summary>
        /// Checks if SPList is initialized and effective permissions contains create permission.
        /// </summary>
        public static bool CanCreateItems(this SPList list)
        {
            return list != null && (list.EffectiveBasePermissions & SPBasePermissions.AddListItems) != SPBasePermissions.EmptyMask;
        }

        /// <summary>
        /// Checks if SPList is initialized and effective permissions contains read permission.
        /// </summary>
        public static bool CanReadItems(this SPList list)
        {
            return list != null && (list.EffectiveBasePermissions & SPBasePermissions.ViewListItems) != SPBasePermissions.EmptyMask;
        }

        /// <summary>
        /// Checks if SPList is initialized and effective permissions contains write permission.
        /// </summary>
        public static bool CanWriteItems(this SPList list)
        {
            return list != null && (list.EffectiveBasePermissions & SPBasePermissions.EditListItems) != SPBasePermissions.EmptyMask;
        }

        /// <summary>
        /// Checks if SPList is initialized and effective permissions contains delete permission.
        /// </summary>
        public static bool CanDeleteItems(this SPList list)
        {
            return list != null && (list.EffectiveBasePermissions & SPBasePermissions.DeleteListItems) != SPBasePermissions.EmptyMask;
        }

        /// <summary>
        /// Get all SPListItem from this SPList.
        /// </summary>
        public static IEnumerable<SPListItem> GetItems(this SPList list)
        {
            return list.GetItemsByQuery(String.Empty);
        }

        /// <summary>
        /// Get IEnumerable of SPListItem where the value of the field equals one the specified IDs from this SPList.
        /// </summary>
        public static IEnumerable<SPListItem> GetItemsById(this SPList list, params int[] values)
        {
            foreach (var batch in (values ?? Enumerable.Empty<int>()).Batch(50))
            {
                string where = null;

                foreach (var condition in batch.Select(x => CAML.Eq(CAML.FieldRef("ID"), CAML.Value(x))))
                {
                    if (where == null)
                    {
                        where = condition;
                    }
                    else
                    {
                        where = CAML.Or(condition, where);
                    }
                }

                CamlQuery query = new CamlQuery(CAML.Where(where));

                var result = list.GetItemsByQuery(query.ToString());

                foreach (var item in result)
                {
                    yield return item;
                }
            }
        }

        /// <summary>
        /// Get IEnumerable of SPListItem where the value of the field equals the specified value from this SPList.
        /// </summary>
        public static IEnumerable<SPListItem> GetItemsByFieldEquals(this SPList list, string field, params string[] values)
        {
            foreach (var batch in (values ?? Enumerable.Repeat(String.Empty, 1)).Batch(50))
            {
                string where = null;

                foreach (var condition in batch.Select(x => String.IsNullOrEmpty(x) ? CAML.IsNull(CAML.FieldRef(field)) : CAML.Eq(CAML.FieldRef(field), CAML.Value(x))))
                {
                    if (where == null)
                    {
                        where = condition;
                    }
                    else
                    {
                        where = CAML.Or(condition, where);
                    }
                }

                CamlQuery query = new CamlQuery(CAML.Where(where));

                var result = list.GetItemsByQuery(query.ToString());

                foreach (var item in result)
                {
                    yield return item;
                }
            }
        }

        /// <summary>
        /// Get IEnumerable of SPListItem where the value of the field contains the specified value from this SPList.
        /// </summary>
        public static IEnumerable<SPListItem> GetItemsByFieldContains(this SPList list, string field, params string[] values)
        {
            foreach (var batch in (values ?? Enumerable.Repeat(String.Empty, 1)).Batch(50))
            {
                string where = null;

                foreach (var condition in batch.Select(x => String.IsNullOrEmpty(x) ? CAML.IsNull(CAML.FieldRef(field)) : CAML.Contains(CAML.FieldRef(field), CAML.Value(x))))
                {
                    if (where == null)
                    {
                        where = condition;
                    }
                    else
                    {
                        where = CAML.Or(condition, where);
                    }
                }

                CamlQuery query = new CamlQuery(CAML.Where(where));

                var result = list.GetItemsByQuery(query.ToString());

                foreach (var item in result)
                {
                    yield return item;
                }
            }
        }

        /// <summary>
        /// Get IEnumerable of SPListItem where the id of the lookup field equals the specified lookupId from this SPList.
        /// </summary>
        public static IEnumerable<SPListItem> GetItemsByFieldLookupId(this SPList list, string field, params int[] values)
        {
            foreach (var batch in (values ?? Enumerable.Repeat(0, 1)).Batch(50))
            {
                string where = null;

                foreach (var condition in batch.Select(x => x == 0 ? CAML.IsNull(CAML.FieldRef(field)) : CAML.Eq(CAML.FieldRefByLookupId(field), CAML.Value(x))))
                {
                    if (where == null)
                    {
                        where = condition;
                    }
                    else
                    {
                        where = CAML.Or(condition, where);
                    }
                }

                CamlQuery query = new CamlQuery(CAML.Where(where));

                var result = list.GetItemsByQuery(query.ToString());

                foreach (var item in result)
                {
                    yield return item;
                }
            }
        }

        /// <summary>
        /// Get IEnumerable of SPListItem where the id of the lookup field equals the specified lookupId from this SPList.
        /// </summary>
        public static IEnumerable<SPListItem> GetItemsByFieldLookupId(this SPList list, string field, params SPFieldLookupValue[] values)
        {
            return list.GetItemsByFieldLookupId(field, values == null ? null : values.Select(x => x.Id()).ToArray());
        }

        /// <summary>
        /// Central Query Method - Get IEnumerable of SPListItem by caml query from this SPList.
        /// </summary>
        public static IEnumerable<SPListItem> GetItemsByQuery(this SPList list, string caml, uint? rowLimit, string[] viewFields)
        {
            if (list != null)
            {
                if (list.ItemCount <= 2000 && rowLimit == null)
                {
                    SPQuery query = new SPQuery();
                    if (viewFields != null) query.ViewFields = String.Join(String.Empty, viewFields.Select(f => CAML.FieldRef(f)).ToArray());
                    query.ViewAttributes = @"Scope=""Recursive""";
                    query.Query = caml;
                    SPListItemCollection items = list.GetItems(query);
                    foreach (SPListItem item in items)
                    {
                        yield return item;
                    }
                }
                else
                {
                    SPQuery query = new SPQuery();
                    if (viewFields != null) query.ViewFields = String.Join(String.Empty, viewFields.Select(f => CAML.FieldRef(f)).ToArray());
                    query.ViewAttributes = @"Scope=""Recursive""";
                    query.Query = caml;
                    query.RowLimit = rowLimit ?? 500;
                    do
                    {
                        SPListItemCollection items = list.GetItems(query);
                        foreach (SPListItem item in items)
                        {
                            yield return item;
                        }
                        query.ListItemCollectionPosition = items.ListItemCollectionPosition;
                    } while (query.ListItemCollectionPosition != null);
                }
            }
        }

        /// <summary>
        /// Get IEnumerable of SPListItem by caml query from this SPList.
        /// </summary>
        public static IEnumerable<SPListItem> GetItemsByQuery(this SPList list, string caml)
        {
            return list.GetItemsByQuery(caml, null, null);
        }

        /// <summary>
        /// Get first or default SPListItem where the value of the field contains the specified value from this SPList.
        /// </summary>
        public static SPListItem GetItemByFieldEquals(this SPList list, string field, string value)
        {
            CamlQuery query = new CamlQuery(
                        CAML.Where(
                        string.IsNullOrEmpty(value) ? CAML.IsNull(CAML.FieldRef(field)) :
                                                      CAML.Eq(
                                                          CAML.FieldRef(field),
                                                          CAML.Value(value))
                        ));
            return list.GetItemsByQuery(query.ToString(), 1, null).FirstOrDefault();
        }

        /// <summary>
        /// Get first or default SPListItem where the value of the field contains the specified value from this SPList.
        /// </summary>
        public static SPListItem GetItemByFieldContains(this SPList list, string field, string value)
        {
            CamlQuery query = new CamlQuery(
                        CAML.Where(
                            CAML.Contains(
                                CAML.FieldRef(field),
                                CAML.Value(value)
                            )
                        ));
            return list.GetItemsByQuery(query.ToString(), 1, null).FirstOrDefault();
        }

        /// <summary>
        /// Get first or default SPListItem where the id of the lookup field equals the specified lookupId from this SPList.
        /// </summary>
        public static SPListItem GetItemByFieldLookupId(this SPList list, string field, int value)
        {
            CamlQuery query = new CamlQuery(
                        CAML.Where(
                            CAML.Eq(
                                CAML.FieldRefByLookupId(field),
                                CAML.Value(value)
                            )
                        ));
            return list.GetItemsByQuery(query.ToString(), 1, null).FirstOrDefault();
        }

        /// <summary>
        /// Get first or default SPListItem where the id of the lookup field equals the specified lookupId from this SPList.
        /// </summary>
        public static SPListItem GetItemByFieldLookupId(this SPList list, string field, SPFieldLookupValue value)
        {
            return list.GetItemByFieldLookupId(field, value.Id());
        }

        /// <summary>
        /// Get first or default SPListItem by caml query from this SPList.
        /// </summary>
        public static SPListItem GetItemByQuery(this SPList list, string caml)
        {
            return list.GetItemsByQuery(caml, 1, null).FirstOrDefault();
        }

        public static SPListItem CreateItem(this SPList list)
        {
            return list.Items.Add();
        }

        #region Wrapped SPListItem extensions

        /// <summary>
        /// Get all wrapped SPListItem from this SPList.
        /// </summary>
        public static IEnumerable<T> GetItems<T>(this SPList list) where T : class, IBaseItem, new()
        {
            return list.GetItems().Select(x => new T() { Item = x });
        }

        /// <summary>
        /// Get IEnumerable of wrapped SPListItem where the ID equals one of the specified values from this SPList.
        /// </summary>
        public static IEnumerable<T> GetItemsById<T>(this SPList list, params int[] values) where T : class, IBaseItem, new()
        {
            return list.GetItemsById(values).Select(x => new T() { Item = x });
        }

        /// <summary>
        /// Get IEnumerable of wrapped SPListItem where the value of the field equals one of the specified values from this SPList.
        /// </summary>
        public static IEnumerable<T> GetItemsByFieldEquals<T>(this SPList list, string field, params string[] values) where T : class, IBaseItem, new()
        {
            return list.GetItemsByFieldEquals(field, values).Select(x => new T() { Item = x });
        }

        /// <summary>
        /// Get IEnumerable of wrapped SPListItem where the value of the field contains one of the specified values from this SPList.
        /// </summary>
        public static IEnumerable<T> GetItemsByFieldContains<T>(this SPList list, string field, params string[] values) where T : class, IBaseItem, new()
        {
            return list.GetItemsByFieldContains(field, values).Select(x => new T() { Item = x });
        }

        /// <summary>
        /// Get IEnumerable of wrapped SPListItem where the id of the lookup field equals one of the specified lookupId values from this SPList.
        /// </summary>
        public static IEnumerable<T> GetItemsByFieldLookupId<T>(this SPList list, string field, params int[] values) where T : class, IBaseItem, new()
        {
            return list.GetItemsByFieldLookupId(field, values).Select(x => new T() { Item = x });
        }

        /// <summary>
        /// Get IEnumerable of wrapped SPListItem by caml query from this SPList.
        /// </summary>
        public static IEnumerable<T> GetItemsByQuery<T>(this SPList list, string caml) where T : class, IBaseItem, new()
        {
            return list.GetItemsByQuery(caml).Select(x => new T() { Item = x });
        }

        /// <summary>
        /// Get IEnumerable of wrapped SPListItem where the value of the field equals the specified value from this SPList.
        /// </summary>
        public static T GetItemByFieldEquals<T>(this SPList list, string field, string value) where T : class, IBaseItem, new()
        {
            var result = list.GetItemByFieldEquals(field, value);
            return result == null ? null : new T() { Item = result };
        }

        /// <summary>
        /// Get first or default wrapped SPListItem where the value of the field contains the specified value from this SPList.
        /// </summary>
        public static T GetItemByFieldContains<T>(this SPList list, string field, string value) where T : class, IBaseItem, new()
        {
            var result = list.GetItemByFieldContains(field, value);
            return result == null ? null : new T() { Item = result };
        }

        /// <summary>
        /// Get first or default wrapped SPListItem where the id of the lookup field equals the specified lookupId from this SPList.
        /// </summary>
        public static T GetItemById<T>(this SPList list, int id) where T : class, IBaseItem, new()
        {
            var result = list.GetItemById(id);
            return result == null ? null : new T() { Item = result };
        }

        /// <summary>
        /// Get first or default wrapped SPListItem where the id of the lookup field equals the specified lookupId from this SPList.
        /// </summary>
        public static T GetItemByFieldLookupId<T>(this SPList list, string field, int lookupId) where T : class, IBaseItem, new()
        {
            var result = list.GetItemByFieldLookupId(field, lookupId);
            return result == null ? null : new T() { Item = result };
        }

        /// <summary>
        /// Get first or default wrapped SPListItem by caml query from this SPList.
        /// </summary>
        public static T GetItemByQuery<T>(this SPList list, string caml) where T : class, IBaseItem, new()
        {
            var result = list.GetItemsByQuery(caml, 1, null).FirstOrDefault();
            return result == null ? null : new T() { Item = result };
        }
        #endregion

        #endregion

        #region SPItem extensions

        /// <summary>
        /// Set field value of this SPListItem by internal name.
        /// </summary>
        public static void SetValue(this SPListItem item, string internalname, object value)
        {
            SPField field = item.Fields.GetFieldByInternalName(internalname);
            item[field.Id] = value;
        }

        /// <summary>
        /// Get field value of this SPListItem by internal name.
        /// </summary>
        public static object GetValue(this SPListItem item, string internalname)
        {
            SPField field = item.Fields.GetFieldByInternalName(internalname);
            return item[field.Id];
        }

        /// <summary>
        /// Get field value as SPFieldLookupValue. 
        /// </summary>
        public static SPFieldLookupValue GetLookupValue(this SPListItem item, string internalname)
        {
            string s = String.Format("{0}", item.GetValue(internalname));
            return String.IsNullOrEmpty(s) ? null : new SPFieldLookupValue(s);
        }

        /// <summary>
        /// Get field value as SPFieldMultiChoiceValue. 
        /// </summary>
        public static SPFieldMultiChoiceValue GetMulitChoiceValue(this SPListItem item, string internalname)
        {
            string s = String.Format("{0}", item.GetValue(internalname));
            return String.IsNullOrEmpty(s) ? null : new SPFieldMultiChoiceValue(s);
        }

        public static SPFieldLookupValue ToLookup(this SPListItem item)
        {
            return item != null ? new SPFieldLookupValue(item.ID, String.Empty) : null;
        }

        /// <summary>
        /// Performs unsafe Update.
        /// </summary>
        public static void UpdateUnsafe(this SPListItem item)
        {
            if (item != null)
            {
                bool allow = item.Web.AllowUnsafeUpdates;
                item.Web.AllowUnsafeUpdates = true;
                item.Update();
                item.Web.AllowUnsafeUpdates = allow;
            }

        }

        /// <summary>
        /// Performs unsafe SystemUpdate.
        /// </summary>
        public static void SystemUpdateUnsafe(this SPListItem item)
        {
            if (item != null)
            {
                bool allow = item.Web.AllowUnsafeUpdates;
                item.Web.AllowUnsafeUpdates = true;
                item.SystemUpdate();
                item.Web.AllowUnsafeUpdates = allow;
            }
        }

        /// <summary>
        /// Performs unsafe Delete.
        /// </summary>
        public static void DeleteUnsafe(this SPListItem item)
        {
            if (item != null)
            {
                bool allow = item.Web.AllowUnsafeUpdates;
                item.Web.AllowUnsafeUpdates = true;
                item.Delete();
                item.Web.AllowUnsafeUpdates = allow;
            }
        }

        public static int Id(this SPFieldLookupValue lookup)
        {
            return lookup != null ? lookup.LookupId : 0;
        }
        public static string Value(this SPFieldLookupValue lookup)
        {
            return lookup != null ? lookup.LookupValue : String.Empty;
        }

        public static IEnumerable<string> Values(this SPFieldMultiChoiceValue value)
        {
            if (value == null || value.Count == 0)
            {
                yield break;
            }
            else
            {
                for (int i = 0; i < value.Count; i++)
                {
                    yield return value[i];
                }
            }
        }

        #endregion
    }
}
